﻿using System;
using System.Collections.Generic;
using System.Text;
using llAma.Backend.Interface;
using dAmn;
using llamaServer;
using llAma.Interface;
using System.Runtime.CompilerServices;

namespace llAma.Base
{
    public abstract class ChannelBase : llamaCommonBase, IChannel
    {

        bool IChannel.Demand(IConnection conn, PermissionRule r)
        {
            return this.Demand(conn, r);
        }
        public virtual bool Demand(IConnection conn, PermissionRule r)
        {
            //We just return true here, Its up to the other class to implement this.
            return true;
        }

        protected abstract IPropertyManager PropertyManager
        {
            get;
        }
        protected ChannelBase()
            : base()
        { }
        string mNamespace;
        public string Namespace
        {
            get { return mNamespace; }
            protected set { mNamespace = value; }
        }
        private Dictionary<Guid, IConnection> clientDict = new Dictionary<Guid, IConnection>();
        protected void AddConnection(IConnection conn)
        {
            lock (clientDict)
            {
                if (clientDict.ContainsKey(conn.UUID))
                {
                    //Silent success? Bug?
                }
                else
                {
                    clientDict[conn.UUID] = conn;
                }
            }
        }
        delegate void dlgAddRemove(IConnection conn);
        protected void RemoveConnection(IConnection conn)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new dlgAddRemove(RemoveConnection), conn);
                return;
            }
            lock (clientDict)
            {
                //Silent success? Bug?
                if (!clientDict.ContainsKey(conn.UUID))
                    return;
                clientDict.Remove(conn.UUID);
            }
        }
        protected IConnection LookupConnection(Guid uuid)
        {
            IConnection ret = null;
            lock (clientDict)
                if (clientDict.ContainsKey(uuid))
                    ret = clientDict[uuid];
            return ret;
        }

        protected IConnection[] GetAllConnectionsPresent()
        {
            lock (clientDict)
            {
                IConnection[] ret = new IConnection[clientDict.Values.Count];
                clientDict.Values.CopyTo(ret, 0);
                return ret;
            }
        }

        void CleanUp(IConnection c)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new dlgAddRemove(CleanUp), c);
                return;
            }
            ((IChannel)this).ExternalPart(c, "connection closed");
        }
        string IChannel.Namespace
        {
            get { return this.Namespace; }
        }
        delegate void dlgHandlePacket(Packet p, object sender);
        void IHandlePackets.HandlePacket(Packet p, IConnection sender)
        {
            this.ExecutePacketEvents(p, sender);
        }

        protected void SendToAll(string data)
        {

            foreach (IConnection c in GetAllConnectionsPresent())
            {
                if (!c.Connected)
                {
                    //If this fires, we could have a bug on our hands.
                    this.CleanUp(c);
                }
                else
                {
                    c.Send(data);
                }
            }
        }
        protected void SendToAll(Packet p)
        {
            SendToAll(p.ToString());
        }
        protected override string GetNamespace()
        {
            return this.Namespace;
        }
        string[] userVars = { "usericon", "symbol", "realname", "typename", "gpc" };
        protected virtual string GetPrivclassName(IConnection conn)
        {
            return string.Empty;
        }
        [PacketHandler("join")]
        protected void handleJoinPacket(Packet p, IConnection conn)
        {
            if (string.Compare(p.param, this.Namespace, true) != 0) return;
            p.param = this.Namespace;
            if (!Demand(conn, new PermissionRule("join", 0, Comparison.NEQ)))
            {
                SendErrorPacketToClient(conn, p, "not privileged");
                return;
            }
            lock (conn)
            {
                //Ok so they can join, are they already joined though?
                if (LookupConnection(conn.UUID) != null)
                {
                    SendErrorPacketToClient(conn, p, "already joined");
                    return;
                }
                //No they are not.
                Packet toRoom = EmptyRecvPacket;
                Packet innerPacket = Packet.EmptyPacket;
                Packet argsPacket = Packet.EmptyPacket;
                innerPacket.cmd = "join";
                innerPacket.param = conn.Username;
                if (Demand(conn, new PermissionRule("shownotice", 0, Comparison.NEQ)))
                    innerPacket.args["s"] = "1";
                else
                    innerPacket.args["s"] = "0";
                OnGetMemberInfo(conn, argsPacket);
                innerPacket.SubPacket = argsPacket;
                toRoom.SubPacket = innerPacket;
                SendToAll(toRoom);
                conn.AddChannel(this);
                SendErrorPacketToClient(conn, p, "ok");
                //SendPropertiesToClient(conn);
                //This has to be handled here, Due to security and sanity checks.
                //Everything else, will not be passed along, if the user isnt joined.
                AddConnection(conn);
                PropertyManager.HandlePacket(p, conn);
            }
        }

        [PacketHandler("*")]
        protected void handleAllPacketsForPropertyManager(Packet p, IConnection conn)
        {
            //Look up permissions:
            if (string.Compare(p.param, this.Namespace, true) != 0) return;
            if (!conn.CurrentUser.SecurityCheck(p))
            {
                SendErrorPacketToClient(conn, p, "not privileged");
                return;
            }
            if (p.cmd != "join" && p.cmd != "part")
            {
                if (this.LookupConnection(conn.UUID) == null)
                {
                    SendErrorPacketToClient(conn, p, "not joined");
                    return;
                }
                PropertyManager.HandlePacket(p, conn);
            }
        }
        protected override bool PacketPredicate(Packet p)
        {
            p.param = Namespace;
            return true;
        }

        [PacketHandler("part")]
        protected void handlePartPacket(Packet p, IConnection conn)
        {
            if (string.Compare(p.param, this.Namespace, true) != 0) return;
            lock (conn)
            {
                if (!conn.CurrentUser.SecurityCheck(p))
                {
                    return;
                }
                //Ok so they can join, are they already joined though?
                if (LookupConnection(conn.UUID) == null)
                {
                    //SendErrorPacketToClient(conn, p, "not joined");
                    return;
                }
                PartUser(conn);

            }
        }

        [PacketHandler("kick")]
        protected void OnKickPacket(Packet p, IConnection conn)
        {
            if (string.Compare(p.param, this.Namespace, true) != 0) return;
            if (!p.args.ContainsKey("u"))
            {
                SendErrorPacketToClient(conn, p, "no user specified.");
            }
            if (!Demand(conn, new PermissionRule("kick", 0, Comparison.NEQ)))
            {
                p.param = Namespace;
                SendErrorPacketToClient(conn, p, "not privileged");
                return;
            }
            
            //We need another check here, To make sure that the PC order of the target is <= our PC order.
            //But this is test code anyway.
            //So for now, if they have the priv, just do it.
            IConnection[] conns = GetAllConnectionsPresent();
            List<IConnection> targetConnections = new List<IConnection>();
            string tgtUsername = p.args["u"].ToLower();
            foreach (IConnection c in conns)
            {
                if (c.CurrentUser.getInfo("username").ToLower() == tgtUsername)
                {
                    targetConnections.Add(c);
                }
            }
            if (targetConnections.Count == 0)
            {
                SendErrorPacketToClient(conn, p, "no such user");
                return;
            }
            //Now we get tricky here.
            tgtUsername = targetConnections[0].CurrentUser.getInfo("username");
            if (!CheckKick(conn, targetConnections[0]))
            {
                SendErrorPacketToClient(conn, p, "not privileged");
                return;
            }
            Packet pktOut = Packet.parse(string.Format("kicked {0}\nby={1}\ns={3}\n\n{2}", Namespace, conn.CurrentUser.getInfo("username"), SanitizeUserInput(p.body)
                , Demand(targetConnections[0], new PermissionRule("shownotice", 0, Comparison.NEQ)) ? 1 : 0));
            for (int x = 0; x < targetConnections.Count; x++)
            {
                this.RemoveConnection(targetConnections[x]);
                targetConnections[x].RemoveChannel(this);
                targetConnections[x].Send(pktOut.ToString());
            }
            pktOut.param = tgtUsername;
            Packet recvOut = EmptyRecvPacket;
            recvOut.body = pktOut.ToString();
            SendToAll(recvOut);
        }
        protected virtual bool CheckKick(IConnection sender, IConnection target)
        {
            return true;
        }
        protected bool KickUser(IUserData user, IUserData sender, string reason)
        {
            IConnection[] conns = GetAllConnectionsPresent();
            List<IConnection> targetConnections = new List<IConnection>();
            string tgtUsername = user.getInfo("username");
            foreach (IConnection c in conns)
            {
                if (c.CurrentUser.getInfo("username").ToLower() == tgtUsername)
                {
                    targetConnections.Add(c);
                }
            }
            if (targetConnections.Count == 0)
                return false;
            Packet pktOut = Packet.parse(string.Format("kicked {0}\nby={1}\ni={3}\n\n{2}", Namespace, sender.getInfo("username"),
                SanitizeUserInput(reason), 
                Demand(targetConnections[0], 
                new PermissionRule("shownotice", 0, Comparison.NEQ)) ? 1 : 0)
                );
            for (int x = 0; x < targetConnections.Count; x++)
            {
                this.RemoveConnection(targetConnections[x]);
                targetConnections[x].RemoveChannel(this);
                targetConnections[x].Send(pktOut.ToString());
            }
            pktOut.param = tgtUsername;
            Packet recvOut = EmptyRecvPacket;
            recvOut.body = pktOut;
            SendToAll(recvOut);
            return true;
        }
        protected virtual void OnGetMemberInfo(IConnection conn, Packet argsPacket)
        {
            string pc = GetPrivclassName(conn);
            if (!string.IsNullOrEmpty(pc))
                argsPacket.args["pc"] = pc;
            for (int x = 0; x < userVars.Length; x++)
            {
                argsPacket.args[userVars[x]] = conn.CurrentUser.getInfo(userVars[x]);
            }
        }

        protected abstract bool CanUserJoin(IConnection sender);

        [PacketHandler("send", "msg")]
        [PacketHandler("send", "action")]
        [PacketHandler("send", "npmsg")]
        protected void handleSendMessagePacket(Packet p, IConnection conn)
        {
            if (string.Compare(p.param, this.Namespace, true) != 0) return;
            if (!Demand(conn, new PermissionRule("msg", 0, Comparison.NEQ)))
            {
                SendErrorPacketToClient(conn, p, "not privileged");
                return;
            }
            lock (conn)
            {
                if (this.LookupConnection(conn.UUID) == null)
                {
                    return;
                }
                Packet snd = Packet.EmptyPacket;
                if (p.SubPacket.cmd == "npmsg")
                    snd.cmd = "msg";
                else
                    snd.cmd = p.SubPacket.cmd;
                snd.param = p.SubPacket.param;
                foreach (KeyValuePair<string, string> kv in p.SubPacket.args)
                {
                    snd.args[kv.Key.ToLower()] = kv.Value; //Copy args so clients can use them.
                }
                snd.args["from"] = conn.CurrentUser.getInfo("username"); //Then overwrite from in case someone wants to be a smartass.
                if(p.SubPacket.cmd == "npmsg")
                    snd.body = SanitizeUserInput(p.SubPacket.body, false);
                else
                    snd.body = SanitizeUserInput(p.SubPacket.body, true);
                snd.body = snd;
                snd.args.Clear();
                snd.cmd = "recv";
                snd.param = this.Namespace;
                SendToAll(snd);

                //SendToAll(string.Format("recv {0}\n\n{1} {2}\nfrom={3}\n\n{4}", this.Namespace.Trim(), p.SubPacket.cmd == "npmsg" ? "msg" : p.SubPacket.cmd, p.SubPacket.param, conn.CurrentUser.getInfo("username"), SanitizeUserInput(p.SubPacket.body)));
            }
        }

        protected bool PartUser(IConnection conn)
        {
            return PartUser(conn, "");
        }
        protected bool PartUser(IConnection conn, string reason)
        {
            if (LookupConnection(conn.UUID) == null)
                return false;
            this.RemoveConnection(conn);
            conn.RemoveChannel(this);
            //send a part event.
            Packet p = Packet.EmptyPacket;
            p.cmd = "part";
            p.param = this.Namespace;
            if (!string.IsNullOrEmpty(reason))
                SendErrorPacketToClient(conn, p, reason);
            else
                SendErrorPacketToClient(conn, p, "ok");
            Packet ret = EmptyRecvPacket;
            Packet sub = Packet.EmptyPacket;
            sub.cmd = "part";
            if (!string.IsNullOrEmpty(reason))
                sub.args["r"] = reason;
            if (Demand(conn, new PermissionRule("shownotice", 0, Comparison.NEQ)))
                sub.args["s"] = "1";
            else
                sub.args["s"] = "0";
            sub.param = conn.Username;
            ret.SubPacket = sub;
            SendToAll(ret);
            return true;
        }

        void IChannel.ExternalPart(IConnection conn, string p)
        {
            PartUser(conn, p);
        }


        void IChannel.SendToAll(string p)
        {
            SendToAll(p);
        }


        IConnection[] IChannel.Connections
        {
            get { return GetAllConnectionsPresent(); }
        }
    }
}
